package luceneqe.modelsIR;

import java.io.IOException;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermPositions;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Similarity;

import luceneqe.utils.BM25Parameters;
import luceneqe.utils.CollectionStats;

//import bm25stats.CollectionStats;

public class BM25TermScorer extends Scorer {

	//public final static float B = 0.67f;
	//public final static float K1= 3.8f;
	// AVG_length Yahoo answers
	//public final float AVG_LENGTH = 105.4f;//Avg length documents in the collection
	//AVG length Trec4 + Trec5
	//public final float AVG_LENGTH = 1040.62f;//Avg length documents in the collection
	//AVG length Robust CLEF LA+GH
	//public final float AVG_LENGTH = 598.288f;//Avg length documents in the collection
	
	
	private TermPositions[] termPositions;
	private Term term;
	private IndexReader idx;
	private String[] fields;
	private boolean[] state;
	private int currentPosition = Integer.MAX_VALUE;
	private boolean init = false;
	private float[] results;
	private int[][] positions;
	private byte[] norms;
	private float boost;
	private float idf;
	

	//private static String FIELD = "text";
	

	
	protected BM25TermScorer(Similarity sim) {
		super(sim);
	}

	public BM25TermScorer(Term term, Similarity similarity, String[] fields,
			IndexReader idx, byte[] norms, float idf, float boost) throws IOException {
		this(similarity);
		this.idx = idx;
		this.term = term;
		this.fields = fields;
		this.boost= boost;
		this.termPositions = new TermPositions[this.fields.length];
		this.state = new boolean[this.fields.length];
		this.results = new float[this.fields.length];
		this.positions = new int[this.fields.length][];
		this.norms= norms;
		this.idf= idf;
		
		for (int i = 0; i < this.state.length; i++) {
			this.termPositions[i] = this.idx.termPositions();
			this.termPositions[i].seek(new Term(this.fields[i], this.term
					.text()));
			this.results[i] = 0f;
		}

	}

	@Override
	public int doc() {
		return this.currentPosition;
	}

	@Override
	public Explanation explain(int doc) throws IOException {
		Explanation exp = new Explanation();
		if (this.skipTo(doc)) {
			exp = new Explanation(this.score(), "Score for term '"
					+ this.term.text() + "'");
			for (int i = 0; i < this.results.length; i++) {
				float norm = (float) CollectionStats.getAvgDocumentLength();
				float res = this.results[i] / norm;
				String strPos = "";
				if (res > 0) {
					strPos = "Positions -->[";
					for (int k = 0; k < this.results[i]; k++)
						strPos += this.positions[i][k] + ",";
					strPos = strPos.substring(0, strPos.length() - 1);
					strPos += "]";

				}
				exp.addDetail(new Explanation(res, "Field '" + this.fields[i]
						+ "' " + this.results[i] + " /" + norm + " IDF: " + this.idf + " Boost: " + this.boost + " " + strPos));
			}

		} else
			exp = new Explanation(0f, "Score for term '" + this.term.text()
					+ "'");
		return exp;
	}

	private void initState() throws IOException {
		this.init = true;
		for (int i = 0; i < this.state.length; i++) {
			this.state[i] = this.termPositions[i].next();
		}
	}

	@Override
	public boolean next() throws IOException {
		boolean result = false;

		// FILL STATE JUST FIRST TIME THAT IS INVOKED
		if (!this.init)
			this.initState();

		int position = Integer.MAX_VALUE;
		// LOOK FOR LOWER DOCUMENTS THAT MATCH TERM AND CALCULATE POSITION
		for (int i = 0; i < this.termPositions.length; i++) {
			this.results[i] = 0f;
			if (this.state[i] && (this.termPositions[i].doc() < position)) {
				position = this.termPositions[i].doc();
				this.currentPosition = position;
			}
		}
		// CALCULATE SCORE AND ADVANCE TERM POSITIONS
		for (int i = 0; i < this.termPositions.length; i++) {
			if (this.state[i]
					&& (this.termPositions[i].doc() == this.currentPosition)) {

				this.results[i] = this.termPositions[i].freq();
				// FILL POSITIONS
				this.positions[i] = this
						.fillPositions(i, (int) this.results[i]);
				this.state[i] = this.termPositions[i].next();
				result = true;
			}
		}
		return result;
	}

	private int[] fillPositions(int field, int freq) throws IOException {
		int[] pos = new int[freq];
		for (int i = 0; i < freq; i++)
			pos[i] = this.termPositions[field].nextPosition() + 1;
		return pos;
	}

	private int getFieldPosition(String field) {
		for (int i = 0; i < this.fields.length; i++) {
			if (this.fields[i].equalsIgnoreCase(field))
				return i;
		}
		return -1;

	}
	
	public Term getTerm()
	{
		return this.term;
	}

	public float score() throws IOException{//0.04f
		
		float tf = this.getSimilarity().tf(this.results[this.getFieldPosition(this.fields[0])]);
		float dl = (float)(Similarity.decodeNorm(norms[this.doc()]));//Longitud aproximada
		//float dl = (float)CollectionStats.get(this.doc()).getLength();//Longitud exacta
		tf = tf/((1-BM25Parameters.getB())+BM25Parameters.getB()*(dl/CollectionStats.getAvgDocumentLength())); // Soft-Normalisation of tf
		tf = ((1+BM25Parameters.getK1()) * tf) / (BM25Parameters.getK1()+tf);
		
		tf = tf * idf * boost;
		return tf;
	}
	
//	//Este m�todo devuelve la longitud de un documento para calcular BM25
//	private double getDocumentLength(IndexReader ir, int docId)
//	{
//		double length = 1.0;
//		try {
//			Document doc = ir.document(docId);
//			String stringLength = doc.getField("length").stringValue();
//			length = new Double(stringLength).doubleValue();
//		} catch (IOException e) {
//			System.err.println("Document not found");
//		}
//		return length;
//	}
	
	public void reset() throws IOException {
		for (int i = 0; i < this.fields.length; i++) {

			this.termPositions[i] = this.idx.termPositions();
			this.termPositions[i].seek(new Term(this.fields[i], this.term
					.text()));
		}
		this.currentPosition = Integer.MAX_VALUE;
		this.initState();
	}

	@Override
	public boolean skipTo(int target) throws IOException {

		this.reset();
		boolean found = false;
		this.currentPosition = 0;
		while (!found && this.next() && target >= this.doc())
			if (target == this.doc())
				found = true;
		return found;
	}
}
